using UnityEngine;
using System.Collections;
using System.Collections.Generic;



public class BM_Utils
{
	#region Screen
	
	public enum DeviceOrientation
	{
		ORIENTATION_LANDSCAPE
	,	ORIENTATION_PORTRAIT
	}
	
	private static float						m_fLandscapeHeightNormalized = -1.0f;
	private static float						m_fLandscapeVerticalOffsetNormalized = -1.0f;
	private static float						m_fScaleFactor = -1.0f;
	
	private static readonly Vector2				m_fScreenSizeRef_Portrait 	= new Vector2(1080.0f, 1920.0f);
	private static readonly Vector2				m_fScreenSizeRef_Landscape = new Vector2(1920.0f, 1080.0f);
//	private static readonly Vector2				m_fScreenSizeRef_Ads = new Vector2(0.0f, 50.0f);

	
	public static DeviceOrientation DeviceOrientationType
	{
		get
		{
			if(ScreenWidth > ScreenHeight)
				return DeviceOrientation.ORIENTATION_LANDSCAPE;
			else
				return DeviceOrientation.ORIENTATION_PORTRAIT;
		}
	}
	
	public static Vector2 RefScreenSize
	{
		get
		{
			if(DeviceOrientationType == DeviceOrientation.ORIENTATION_LANDSCAPE)
				return m_fScreenSizeRef_Landscape;
			else
				return m_fScreenSizeRef_Portrait;
		}
		
	}
	
	public static float RefAspectRatio
	{
		get
		{
			if(DeviceOrientationType == DeviceOrientation.ORIENTATION_LANDSCAPE)
				return RefScreenSize.x/RefScreenSize.y;
			else
				return RefScreenSize.y/RefScreenSize.x;
		}
	}
	
	public static float ScreenWidth
	{
		get { return (float)Screen.width; }
	}
	
	public static float ScreenHeight
	{
		get { return (float)Screen.height; }
	}
	
	public static Vector2 ScreenSize
	{
		get { return new Vector2(ScreenWidth, ScreenHeight); }
	}
	
	public static float ActualAspectRatio
	{
		get
		{
			if(DeviceOrientationType == DeviceOrientation.ORIENTATION_LANDSCAPE)
				return (ScreenWidth/ScreenHeight);
			else
				return (ScreenHeight/ScreenWidth);
		}
	}
	
	public static float LandscapeHeightNormalized
	{
		get { return m_fLandscapeHeightNormalized; }
	}
	
	public static float LandscapeVerticalOffsetNormalized
	{
		get { return m_fLandscapeVerticalOffsetNormalized; }
	}
	
	public static float LandscapeHeight
	{
		get { return (ScreenHeight*LandscapeHeightNormalized)+2; }
	}
	
	public static float ScaleFactor
	{
		get { return m_fScaleFactor; }
	}	
		
	public static void InitializeScreenStuff()
	{
		// determino la dimensione verticale della viewport (nel caso di versione landscape se l'a-r non corrisponde a quello nativo mostro le bande orizzontali nere)
/*		m_fLandscapeHeightNormalized = 1.0f;
		if(CM_GameConfig.Instance().UseAdMobPlugin)
		{
			float fAdsScreenHeight_Ref = ScreenSize.y;
			if(Screen.dpi > 0)
				fAdsScreenHeight_Ref -= m_fScreenSizeRef_Ads.y * (Screen.dpi / 160.0f);
			else
				fAdsScreenHeight_Ref -= m_fScreenSizeRef_Ads.y*2.0f;
			
			m_fLandscapeHeightNormalized = fAdsScreenHeight_Ref / ScreenSize.y;
		}
		else */if(DeviceOrientationType == DeviceOrientation.ORIENTATION_LANDSCAPE)
		{
			m_fLandscapeHeightNormalized = ActualAspectRatio / RefAspectRatio;
			if(m_fLandscapeHeightNormalized > 1)
				m_fLandscapeHeightNormalized = 1.0f / m_fLandscapeHeightNormalized;
		}
		
		
		// determino l'offset verticale della viewport
		m_fLandscapeVerticalOffsetNormalized = 0.0f;
/*		if(CM_GameConfig.Instance().UseAdMobPlugin)
			m_fLandscapeVerticalOffsetNormalized = (1.0f - LandscapeHeightNormalized);
		else */if(DeviceOrientationType == DeviceOrientation.ORIENTATION_LANDSCAPE)
			m_fLandscapeVerticalOffsetNormalized = (1.0f - LandscapeHeightNormalized) * 0.5f;
		
		
		// determino il fattore di scalatura per le risoluzioni non-native
		if(DeviceOrientationType == DeviceOrientation.ORIENTATION_LANDSCAPE)
			m_fScaleFactor = ScreenHeight/BM_Utils.RefScreenSize.y;
		else
		{
			if(BM_Utils.ActualAspectRatio > BM_Utils.RefAspectRatio)
				m_fScaleFactor = BM_Utils.ScreenWidth/BM_Utils.RefScreenSize.x;
			else
				m_fScaleFactor = BM_Utils.ScreenHeight/BM_Utils.RefScreenSize.y;
		}
		m_fScaleFactor *= BM_Utils.LandscapeHeightNormalized;
	}
	
	#endregion
	
	
	#region Interpolators
	
	/// <summary>
	/// Calcola e imposta i parametri di interpolazione per far si che un oggetto raggiunga un punto dato
	/// </summary>
	/// <param name="vFromPoint">Punto di partenza</param>
	/// <param name="vToPoint">Punto di arrivo</param>
	/// <param name="fSpeed">Velocità (unità/sec)</param>
	/// <param name="fStartTime">Istante in cui deve partire l'interpolazione</param>
	/// <param name="rInterpolator">Riferimento dell'interpolatore da gestire</param>
//	static public void StartVector3Interpolation(Vector3 vFromPoint, Vector3 vToPoint, float fSpeed, float fStartTime, SV_Vector3Interpolator rInterpolator)
//	{
//		float fDistance = Vector3.Distance(vToPoint, vFromPoint);
//		rInterpolator.SetInterpolationValues(vFromPoint, vToPoint, fStartTime, fStartTime+(fDistance/fSpeed), SV_IntMethod.Options.OPT_NONE);
//	}
//	
//	static public void StartDirectionInterpolation(Transform rObjectTransform, Vector3 vLocalForward, Vector3 vLocalUp, Vector3 vPointToLook, Vector3 vWorldUp, float fSpeed, bool bSpeedAsTime, float fStartTime, SV_QuaternionInterpolator rInterpolator)
//	{
//		Quaternion qEndRotation = LookAtRotationQuaternion(vLocalForward, vLocalUp, vPointToLook, vWorldUp)*rObjectTransform.rotation;
//		
//		float fActualSpeed = fSpeed;
//		if(bSpeedAsTime)
//		{
//			float fAngle = Quaternion.Angle(rObjectTransform.rotation, qEndRotation);
//			fActualSpeed = fAngle/fSpeed; // fSpeed è in realtà il tempo
//		}
//		
//		rInterpolator.SetInterpolationValues(rObjectTransform.rotation, qEndRotation, fStartTime, fActualSpeed);
//	}
	
	public static Vector3 SpringInterpolationVector(Vector3 vStart, Vector3 vEnd, float fTime, float fSpeedRatio)
	{
		return SpringInterpolationVector(vStart, vEnd, fTime, Time.deltaTime, fSpeedRatio);
	}
	
	public static Vector3 SpringInterpolationVector(Vector3 vStart, Vector3 vEnd, float fTime, float fDeltaTime, float fSpeedRatio)
	{
		Vector3 vRVal = vEnd;
		
		Vector3 vMovement = vEnd - vStart;
		if(Mathf.Approximately(vMovement.magnitude, 0.0f) == false)
		{
			float fSpeed = vMovement.magnitude / fTime;
			
			// calcolo lo spazio percorso e, se necessario, eseguo il clamp
			float fSpace = fSpeed * fDeltaTime * fSpeedRatio;
			if(fSpace > vMovement.magnitude)
				fSpace = vMovement.magnitude;
			
			vMovement.Normalize();
			vRVal = vStart + vMovement * fSpace;
		}
		
		return vRVal;
	}
	
	public static float SpringInterpolationFloat(float fStart, float fEnd, float fTime)
	{
		return SpringInterpolationFloat(fStart, fEnd, fTime, Time.deltaTime);
	}
	
	private static float SpringInterpolationFloat(float fStart, float fEnd, float fTime, float fDeltaTime)
	{
		float fRVal = fEnd;
		
		float fMovement = fEnd - fStart;
		if(Mathf.Abs(fMovement) > 0.01f)
		{
			float fSpeed = fMovement / fTime;
			float fSpace = fSpeed * fDeltaTime;
			
			if(Mathf.Abs(fSpace) > Mathf.Abs(fMovement))
				fSpace = fMovement;

			fRVal = fStart + fSpace;
		}

		return fRVal;
	}
	
	#endregion
	
	
	#region Geometry
	
	public enum AxisLabel
	{
		AXIS_NONE = -1
	,	AXIS_UP = 0
	,	AXIS_DOWN
	,	AXIS_RIGHT
	,	AXIS_LEFT
	,	AXIS_FORWARD
	,	AXIS_BACK
	}
	
	static private bool			m_bRotMat_HalfPi_Clockwise_Initialized = false; // pezzotto che devo fare perché Matrix4x4 non ha un construttore con parametri... vaffanculo unity...
	static private Matrix4x4	m_mRotMat_HalfPi_Clockwise = Matrix4x4.zero;
	static private bool			m_bRotMat_HalfPi_CounterClockwise_Initialized = false; // pezzotto che devo fare perché Matrix4x4 non ha un construttore con parametri... vaffanculo unity...
	static private Matrix4x4	m_mRotMat_HalfPi_CounterClockwise = Matrix4x4.zero;
	
	static public Matrix4x4 RotMat_HalfPi_Clockwise
	{
		get
		{
			if(!m_bRotMat_HalfPi_Clockwise_Initialized)
			{
				m_mRotMat_HalfPi_Clockwise[0, 1] = 1.0f;
				m_mRotMat_HalfPi_Clockwise[1, 0] = -1.0f;
				m_mRotMat_HalfPi_Clockwise[2, 2] = 1.0f;
				m_bRotMat_HalfPi_Clockwise_Initialized = true;
			}
			
			return m_mRotMat_HalfPi_Clockwise;
		}
	}
	
	static public Matrix4x4 RotMat_HalfPi_CounterClockwise
	{
		get
		{
			if(!m_bRotMat_HalfPi_CounterClockwise_Initialized)
			{
				m_mRotMat_HalfPi_CounterClockwise[0, 1] = -1.0f;
				m_mRotMat_HalfPi_CounterClockwise[1, 0] = 1.0f;
				m_mRotMat_HalfPi_CounterClockwise[2, 2] = 1.0f;
				m_bRotMat_HalfPi_CounterClockwise_Initialized = true;
			}
			
			return m_mRotMat_HalfPi_CounterClockwise;
		}
	}
	
	/// <summary>
	/// Calcola se un versore si trova sulla destra o sulla sinistra rispetto ad un altro
	/// </summary>
	/// <param name="vFromDir">Il versore di riferimento</param>
	/// <param name="vToDir">Il versore su cui effettuare il controllo</param>
	/// <param name="vUpDir">Versore Up dell'oggetto</param>
	static public DirectionLabels GetAngleDir(Vector3 vFromDir, Vector3 vToDir, Vector3 vUpDir)
	{
		DirectionLabels eRVal = DirectionLabels.DIR_NONE;
					
    	Vector3 vNormal = Vector3.Cross(vFromDir, vToDir);
		vNormal.Normalize();
  		float fDir = Vector3.Dot(vNormal, vUpDir);
		
		if(fDir > 0.0f)
		    eRVal = DirectionLabels.DIR_WEST;
		else
		    eRVal = DirectionLabels.DIR_EAST;
		
		return eRVal;
	}
	
	/// <summary>
	/// Dato un intervallo angolare approssima la rotazione data all'angolo di riferimento più vicino
	/// </summary>
	/// <param name="fAngle">Rotazione da approssimare</param>
	/// <param name="fInterval">Intervallo angolare</param>
	static public float RoundAngle(float fAngle, float fInterval)
	{
		if(Debug.isDebugBuild && fInterval < 0.0f)
			Debug.LogError("Attenzione intervallo minore di zero!");
		
		// riporto gli angoli entro un range tra 0 e 360°
		fAngle = fAngle-(Mathf.FloorToInt(fAngle/360.0f)*360.0f);
		fInterval = fInterval-(Mathf.FloorToInt(fInterval/360.0f)*360.0f);
		
		int iRawDir = Mathf.FloorToInt((fAngle/fInterval)+0.5f);
		return (iRawDir*fInterval);
	}
	
	/// <summary>
	/// Dato un angolo controlla se cade tra 0 e fHalfRange e tra 0 e -fHalfRange
	/// </summary>
	/// <param name="fAngle">Angolo da clampare</param>
	/// <param name="fHalfRange">Metà del range in cui l'angolo deve cadere</param>
	static public float ClampAngleIntoRange(float fAngle, float fHalfRange)
	{
		float fRVal = fAngle;
		
		float f180DegXAngle = fAngle > 180.0f ? fAngle-360.0f : fAngle;
		if(f180DegXAngle > fHalfRange)
			fRVal = fHalfRange;
		else if(f180DegXAngle < -fHalfRange)
			fRVal = 360-fHalfRange;
		
		return fRVal;
	}
	
	/// <summary>
	/// Data una rotazione garantisce che yaw, pitch e roll cadano all'interno dei vincoli dati
	/// </summary>
	/// <param name="qRotation">Rotazione da clampare</param>
	/// <param name="vRotationConstraints">Vincoli</param>
	static public Quaternion ClampRotation(Quaternion qRotation, Vector3 vRotationConstraints)
	{
		Vector3 vEulerAngles = qRotation.eulerAngles;
		vEulerAngles.x = ClampAngleIntoRange(vEulerAngles.x, vRotationConstraints.x);
		vEulerAngles.y = ClampAngleIntoRange(vEulerAngles.y, vRotationConstraints.y);
		vEulerAngles.z = ClampAngleIntoRange(vEulerAngles.z, vRotationConstraints.z);
		return Quaternion.Euler(vEulerAngles);
	}
	
	/// <summary>
	/// Calcola la distanza tra un punto e una retta
	/// </summary>
	/// <param name="vRay">Retta</param>
	/// <param name="vPoint">Punto</param>
	static public float DistancePointFromRay(Ray vRay, Vector3 vPoint)
	{
		Vector3 VPointFromOrigin = vPoint - vRay.origin;
		Vector3 fProjectedPoint = Vector3.Dot(VPointFromOrigin, vRay.direction)*vRay.direction + vRay.origin;
		return Vector3.Distance(fProjectedPoint, vPoint);
	}
	
	/// <summary>
	/// Controlla se due punti sono a distanza entro la soglia data
	/// </summary>
	/// <param name="vFirstPoint">Primo punto</param>
	/// <param name="vSecondPoint">Secondo punto</param>
	/// <param name="fDistanceThreshold">Soglia entro cui devono trovarsi i due punti</param>
	static public bool PointsAreClose(Vector3 vFirstPoint, Vector3 vSecondPoint, float fDistanceThreshold)
	{
		bool bRVal = false;
		if(Vector3.Distance(vFirstPoint, vSecondPoint) < fDistanceThreshold)
			bRVal = true;
		return bRVal;
	}
	
	/// <summary>
	/// Dato un orientamento di partenza produce la rotazione necessaria per allineare l'asse forward verso il punto dato
	/// </summary>
	/// <returns>Il quaternione che rappresenta la rotazione da compiere per allinearsi con il punto dato</returns>
	/// <param name='vLocalForward'>Forward dell'oggetto da allineare</param>
	/// <param name='vLocalUp'>Up dell'oggetto da allineare</param>
	/// <param name='vTargetToLook'>Punto (rispetto al s.d.r. dell'oggetto) verso cui orientarsi</param>
	/// <param name='vWorldUp'>Versore Up di riferimento</param>/
	static public Matrix4x4 LookAtRotationMatrix(Vector3 vLocalForward, Vector3 vLocalUp, Vector3 vTargetToLook, Vector3 vWorldUp)
	{
		// assumo che vLocalForward, vLocalUp e vWorldUp siano normalizzati
		
		// completo la terna che rappresenta l'orientamento del soggetto e costruisco la relativa matrice
		Vector3 vLocalRight = Vector3.Cross(vLocalUp, vLocalForward).normalized;
		
		// avrò bisogno della matrice inversa, perciò essendo una matrice ortonormale posso ottenerla costruendo la matrice trasposta...
		Matrix4x4 mInverseLocalMatrix = new Matrix4x4();
		mInverseLocalMatrix.m00 = vLocalRight.x; mInverseLocalMatrix.m01 = vLocalRight.y; mInverseLocalMatrix.m02 = vLocalRight.z;
		mInverseLocalMatrix.m10 = vLocalUp.x; mInverseLocalMatrix.m11 = vLocalUp.y; mInverseLocalMatrix.m12 = vLocalUp.z;
		mInverseLocalMatrix.m20 = vLocalForward.x; mInverseLocalMatrix.m21 = vLocalForward.y; mInverseLocalMatrix.m22 = vLocalForward.z;
		
		// completo la terna che rappresenta l'orientamento da raggiungere e costruisco la relativa matrice
		Vector3 vWorldForward = vTargetToLook.normalized;
		Vector3 vWorldRight = Vector3.Cross(vWorldUp, vWorldForward);
		Vector3 vOrthoWorldUp = Vector3.Cross(vWorldForward, vWorldRight);
		Matrix4x4 mWorldMatrix = new Matrix4x4();
		mWorldMatrix.m00 = vWorldRight.x; mWorldMatrix.m10 = vWorldRight.y; mWorldMatrix.m20 = vWorldRight.z;
		mWorldMatrix.m01 = vOrthoWorldUp.x; mWorldMatrix.m11 = vOrthoWorldUp.y; mWorldMatrix.m21 = vOrthoWorldUp.z;
		mWorldMatrix.m02 = vWorldForward.x; mWorldMatrix.m12 = vWorldForward.y; mWorldMatrix.m22 = vWorldForward.z;
		
		// calcolo la matrice di rotazione per passare da un orientamento all'altro e costruisco il quaternione
		return (mWorldMatrix*mInverseLocalMatrix);
	}
	
	static public Quaternion LookAtRotationQuaternion(Vector3 vLocalForward, Vector3 vLocalUp, Vector3 vTargetToLook, Vector3 vWorldUp)
	{
		Matrix4x4 mMatrix = LookAtRotationMatrix(vLocalForward, vLocalUp, vTargetToLook, vWorldUp);
		return CreateQuaternionFromMatrix(ref mMatrix);
	}
	
	// ref: http://clb.demon.fi/MathGeoLib/docs/Quat.cpp_code.html#315
	static public Quaternion CreateQuaternionFromMatrix(ref Matrix4x4 mRotation)
	{
		float fW = 0.0f;
		float fX = 0.0f;
		float fY = 0.0f;
		float fZ = 0.0f;
		
		float r = mRotation.m00 + mRotation.m11 + mRotation.m22;
		if(r > 0)
		{
			fW = Mathf.Sqrt(r + 1.0f) * 0.5f;
			float inv4w = 1.0f / (4.0f * fW);
			fX = (mRotation.m21 - mRotation.m12) * inv4w;
			fY = (mRotation.m02 - mRotation.m20) * inv4w;
			fZ = (mRotation.m10 - mRotation.m01) * inv4w;
		}
		else if (mRotation.m00 > mRotation.m11 && mRotation.m00 > mRotation.m22)
		{
			fX = Mathf.Sqrt(1.0f + mRotation.m00 - mRotation.m11 - mRotation.m22) * 0.5f;
			float x4 = 1.0f / (4.0f * fX);
			fY = (mRotation.m01 + mRotation.m10) * x4;
			fZ = (mRotation.m02 + mRotation.m20) * x4;
			fW = (mRotation.m21 - mRotation.m12) * x4;
		}
		else if (mRotation.m11 > mRotation.m22)
		{
			fY = Mathf.Sqrt(1.0f + mRotation.m11 - mRotation.m00 - mRotation.m22) * 0.5f;
			float y4 = 1.0f / (4.0f * fY);
			fX = (mRotation.m01 + mRotation.m10) * y4;
			fZ = (mRotation.m12 + mRotation.m21) * y4;
			fW = (mRotation.m02 - mRotation.m20) * y4;
		}
		else
		{
			fZ = Mathf.Sqrt(1.0f + mRotation.m22 - mRotation.m00 - mRotation.m11) * 0.5f;
			float z4 = 1.0f / (4.0f * fZ);
			fX = (mRotation.m02 + mRotation.m20) * z4;
			fY = (mRotation.m12 + mRotation.m21) * z4;
			fW = (mRotation.m10 - mRotation.m01) * z4;
		}
		
		return new Quaternion(fX, fY, fZ, fW);
	}
	
	static public Quaternion CreateQuaternionFromAxis(Vector3 vRight, Vector3 vUp, Vector3 vForward)
	{
		Matrix4x4 mMatrix = new Matrix4x4();
		mMatrix.m00 = vRight.x; mMatrix.m10 = vRight.y; mMatrix.m20 = vRight.z;
		mMatrix.m01 = vUp.x; mMatrix.m11 = vUp.y; mMatrix.m21 = vUp.z;
		mMatrix.m02 = vForward.x; mMatrix.m12 = vForward.y; mMatrix.m22 = vForward.z; 
		return CreateQuaternionFromMatrix(ref mMatrix);
	}
	
	public enum DirectionLabels
	{
		DIR_NONE = -1
	,	DIR_NORTH = 0
	,	DIR_EAST
	,	DIR_SOUTH
	,	DIR_WEST
	,	DIR_NORTH_EAST
	,	DIR_SOUTH_EAST
	,	DIR_SOUTH_WEST
	,	DIR_NORTH_WEST
	}
	
	public enum DirectionTypes
	{
		DIRTYPE_NONE = -1
	,	DIRTYPE_HORIZONTAL = 0
	,	DIRTYPE_VERTICAL
	,	DIRTYPE_DIAGONAL
	}
	
	/// <summary>
	/// Data una rotazione, restituisce la label della direzione più vicina
	/// </summary>
	/// <param name="fRotation">Rotazione da approssimare</param>
	static public DirectionLabels GetDirectionLabelFromRotation(float fRotation)
	{
		DirectionLabels eRVal = DirectionLabels.DIR_NONE;
		fRotation = fRotation-(Mathf.FloorToInt(fRotation/360.0f)*360.0f);
		int iRawDir = Mathf.FloorToInt((fRotation/45.0f)+0.5f);
		switch(iRawDir)
		{
		case 0:
		case 8:
			eRVal = DirectionLabels.DIR_EAST;
			break;
			
		case 1:
			eRVal = DirectionLabels.DIR_NORTH_EAST;
			break;
			
		case 2:
			eRVal = DirectionLabels.DIR_NORTH;
			break;
			
		case 3:
			eRVal = DirectionLabels.DIR_NORTH_WEST;
			break;
			
		case 4:
			eRVal = DirectionLabels.DIR_WEST;
			break;
			
		case 5:
			eRVal = DirectionLabels.DIR_SOUTH_WEST;
			break;
			
		case 6:
			eRVal = DirectionLabels.DIR_SOUTH;
			break;
			
		case 7:
			eRVal = DirectionLabels.DIR_SOUTH_EAST;
			break;
			
		default:
			Debug.LogError("CM_Utils.GetDirectionLabelFromRotation(): errore inaspettato!");
			break;
		}
		return eRVal;
	}
	
	static public DirectionLabels GetDirectionLabelFromVector(Vector3 vVectorDirection)
	{
		DirectionLabels eRVal = DirectionLabels.DIR_NONE;
		
		if(vVectorDirection.x != 0.0f || vVectorDirection.y != 0.0f)
		{
			float fAngle = Mathf.Atan2(vVectorDirection.y, vVectorDirection.x)*Mathf.Rad2Deg;
			fAngle = (fAngle < 0.0f) ? 360.0f + fAngle : fAngle;
			eRVal = GetDirectionLabelFromRotation(fAngle);
		}
		
		return eRVal;
	}
	
	static public Vector3 Get8DirVectorFromDirectionLabel(DirectionLabels eDirectionLabel)
	{
		Vector3 vRVal = Vector3.zero;
		
		switch(eDirectionLabel)
		{
		case DirectionLabels.DIR_EAST:
			vRVal = new Vector3(1.0f, 0.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_NORTH_EAST:
			vRVal = new Vector3(1.0f, 1.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_NORTH:
			vRVal = new Vector3(0.0f, 1.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_NORTH_WEST:
			vRVal = new Vector3(-1.0f, 1.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_WEST:
			vRVal = new Vector3(-1.0f, 0.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_SOUTH_WEST:
			vRVal = new Vector3(-1.0f, -1.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_SOUTH:
			vRVal = new Vector3(0.0f, -1.0f, 0.0f);
			break;
			
		case DirectionLabels.DIR_SOUTH_EAST:
			vRVal = new Vector3(1.0f, -1.0f, 0.0f);
			break;

		default:
			Debug.LogError("CM_Utils.Get8DirVectorFromDirectionLabel(): errore inaspettato!");
			break;
		}
		
		vRVal.Normalize();
		return vRVal;
	}
	
	static public Vector3 Get8DirVectorFromRotation(float fRotation)
	{
		DirectionLabels eCurrDir = GetDirectionLabelFromRotation(fRotation);
		return Get8DirVectorFromDirectionLabel(eCurrDir);
	}
	
	static public Vector3 GetDirectionVectorFromRotation(float fRotation)
	{
		Vector3 vRVal = new Vector3(Mathf.Cos(fRotation), Mathf.Sin(fRotation), 0.0f);
		return vRVal.normalized;
	}
	
	static public DirectionTypes GetDirectionType(DirectionLabels eDirectionLabel)
	{
		DirectionTypes eRVal = DirectionTypes.DIRTYPE_NONE;
		
		switch(eDirectionLabel)
		{
		case DirectionLabels.DIR_NORTH:
		case DirectionLabels.DIR_SOUTH:
			eRVal = DirectionTypes.DIRTYPE_VERTICAL;
			break;
			
		case DirectionLabels.DIR_WEST:
		case DirectionLabels.DIR_EAST:
			eRVal = DirectionTypes.DIRTYPE_HORIZONTAL;
			break;
			
		case DirectionLabels.DIR_NORTH_EAST:
		case DirectionLabels.DIR_NORTH_WEST:
		case DirectionLabels.DIR_SOUTH_EAST:
		case DirectionLabels.DIR_SOUTH_WEST:
			eRVal = DirectionTypes.DIRTYPE_DIAGONAL;
			break;
			
		default:
			break;
		}
		
		return eRVal;
	}
	
	static public Vector3 RayPlaneIntersection(Plane pPlane, Vector3 vRayOrigin, Vector3 vRayDirection)
	{
		vRayDirection.Normalize();
		Ray vRay = new Ray(vRayOrigin, vRayDirection);
		
		float fDistance = 0.0f;
		pPlane.Raycast(vRay, out fDistance); 
		
		return vRayOrigin+vRayDirection*fDistance;
	}
	
	/// <summary>
	/// Aggiorna il volume dell'AABB se il punto passato come parametro non è già contenuto
	/// </summary>
	/// <param name="vPoint">Il nuovo punto da testare</param>
	/// <param name="vCurrentMinPoint">Il punto con coordinate minori dell'AABB corrente</param>
	/// <param name="vCurrentMaxPoint">Il punto con coordinate maggiori dell'AABB corrente</param>
	public static void UpdateAABB(Vector3 vPoint, ref Vector3 vCurrentMinPoint, ref Vector3 vCurrentMaxPoint)
	{
		if (vPoint.x < vCurrentMinPoint.x)
			vCurrentMinPoint.x = vPoint.x;
		if (vPoint.y < vCurrentMinPoint.y)
			vCurrentMinPoint.y = vPoint.y;
		if (vPoint.z < vCurrentMinPoint.z)
			vCurrentMinPoint.z = vPoint.z;
		
		if (vPoint.x > vCurrentMaxPoint.x)
			vCurrentMaxPoint.x = vPoint.x;
		if (vPoint.y > vCurrentMaxPoint.y)
			vCurrentMaxPoint.y = vPoint.y;
		if (vPoint.z > vCurrentMaxPoint.z)
			vCurrentMaxPoint.z = vPoint.z;
	}
	
	/// <summary>
	/// Calcola un AABB che contiene tutti i punti indicati
	/// </summary>
	/// <param name="arrPoints">L'array di punti da includere nell'AABB, devono essere Vector3</param>
	/// <param name="vMinPoint">Il punto dell'AABB con coordinate minori</param>
	/// <param name="vMaxPoint">Il punto dell'AABB con coordinate maggiori</param>
	public static void ComputeAABB(ref ArrayList arrPoints, ref Vector3 vMinPoint, ref Vector3 vMaxPoint)
	{
		vMinPoint = new Vector3(float.MaxValue, float.MaxValue, float.MaxValue);
		vMaxPoint = new Vector3(float.MinValue, float.MinValue, float.MinValue);
		
		foreach (Vector3 vPoint in arrPoints)
			UpdateAABB(vPoint, ref vMinPoint, ref vMaxPoint);
	}
	
	public static bool LinesIntersection(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, out Vector2 pIntersection)
	{
		// http://flassari.is/2008/11/line-line-intersection-in-cplusplus/
		pIntersection = Vector2.zero;
			
		// Store the values for fast access and easy equations-to-code conversion
		float x1 = p1.x, x2 = p2.x, x3 = p3.x, x4 = p4.x;
		float y1 = p1.y, y2 = p2.y, y3 = p3.y, y4 = p4.y;
		 
		float d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
		// If d is zero, there is no intersection
		if (Mathf.Approximately(d, 0.0f)) return false;
		 
		// Get the x and y
		float pre = (x1*y2 - y1*x2), post = (x3*y4 - y3*x4);
		float x = ( pre * (x3 - x4) - (x1 - x2) * post ) / d;
		float y = ( pre * (y3 - y4) - (y1 - y2) * post ) / d;
		 
		// Check if the x and y coordinates are within both lines
		if ( x < Mathf.Min(x1, x2) || x > Mathf.Max(x1, x2) ||
		x < Mathf.Min(x3, x4) || x > Mathf.Max(x3, x4) ) return false;
		if ( y < Mathf.Min(y1, y2) || y > Mathf.Max(y1, y2) ||
		y < Mathf.Min(y3, y4) || y > Mathf.Max(y3, y4) ) return false;
		 
		// Return the point of intersection
		pIntersection.x = x;
		pIntersection.y = y;
		return true;
	}
	
	#endregion
	
	
	#region Raycasting

	
	/// <summary>
	/// Controlla se un GameObject viene colpito per primo da un raggio dato
	/// </summary>
	/// <param name="vRay">Il raggio su cui fare il controllo</param>
	/// <param name="strTag">Il tag degli oggetti da controllare</param>
	static public bool CheckFirstHitFromRay(Ray vRay, string strTag)
	{
		// int.MaxValue garantisce l'uso di tutti i layer
		RaycastHit hitInfo;
		return CheckFirstHitFromRay(vRay, strTag, int.MaxValue, out hitInfo);
	}
	
	static public bool CheckFirstHitFromRay(Ray vRay, string strTag, int iLayerMask)
	{
		RaycastHit hitInfo;
		return CheckFirstHitFromRay(vRay, strTag, iLayerMask, out hitInfo);
	}
	
	/// <summary>
	/// Controlla se un GameObject viene colpito per primo da un raggio dato, in tal caso restituisce le informazioni sul punto di contatto
	/// </summary>
	/// <param name="vRay">Il raggio su cui fare il controllo</param>
	/// <param name="strTag">Il tag degli oggetti da controllare</param>
	/// <param name="iLayerMask">Il tag degli oggetti da controllare</param>
	/// <param name="rHitInfo">Le informazioni sul punto di contatto</param>
	static public bool CheckFirstHitFromRay(Ray vRay, string strTag, int iLayerMask, out RaycastHit rHitInfo)
	{
		bool bRVal = false;
		
		if(Physics.Raycast(vRay, out rHitInfo, Mathf.Infinity, iLayerMask) && rHitInfo.collider.tag == strTag)
			bRVal = true;
		
		return bRVal;
	}
	
	
	
	
	/// <summary>
	/// Controlla se un GameObject viene colpito per primo da un raggio dato
	/// </summary>
	/// <param name="vFrom">Punto di partenza del raggio</param>
	/// <param name="vTo">Punto di arrivo del raggio</param>
	/// <param name="strTag">Il tag degli oggetti da controllare</param>
	static public bool CheckFirstHitFromPoints(Vector3 vFrom, Vector3 vTo, string strTag)
	{
		// int.MaxValue garantisce l'uso di tutti i layer
		RaycastHit hitInfo;
		return CheckFirstHitFromPoints(vFrom, vTo, strTag, int.MaxValue, out hitInfo);
	}
	
	static public bool CheckFirstHitFromPoints(Vector3 vFrom, Vector3 vTo, string strTag, int iLayerMask)
	{
		RaycastHit hitInfo;
		return CheckFirstHitFromPoints(vFrom, vTo, strTag, iLayerMask, out hitInfo);
	}
	
	/// <summary>
	/// Controlla se un GameObject viene colpito per primo da un raggio dato, in tal caso restituisce le informazioni sul punto di contatto
	/// </summary>
	/// <param name="vFrom">Punto di partenza del raggio</param>
	/// <param name="vTo">Punto di arrivo del raggio</param>
	/// <param name="strTag">Il tag degli oggetti da controllare</param>
	/// <param name="rHitInfo">Le informazioni sul punto di contatto</param>
	static public bool CheckFirstHitFromPoints(Vector3 vFrom, Vector3 vTo, string strTag, int iLayerMask, out RaycastHit rHitInfo)
	{
		bool bRVal = false;
		
		Vector3 vDirection = vTo-vFrom;
		vDirection.Normalize();
		
		if( Physics.Raycast(vFrom, vDirection, out rHitInfo, Vector3.Distance(vTo, vFrom), iLayerMask) &&
			rHitInfo.collider.tag == strTag )
		{
			bRVal = true;
		}
		
//		Debug.DrawLine(vFrom, vTo, (bRVal ? Color.red : Color.blue), 10000.0f);
		
		return bRVal;
	}
	
	
	/// <summary>
	/// Restutisce una lista di oggetti che vengono colpiti dal raggio dato, nel momento in cui il raggio colpisce un GameObject con tag
	/// diverso da quello specificato allora la ricerca si interrompe
	/// </summary>
	/// <param name="vRay">Il raggio su cui fare il controllo</param>
	/// <param name="fDistance">Distanza massima entro cui effettuare il controllo</param>
	/// <param name="strTag">Il tag degli oggetti da controllare</param>
	static public ArrayList CheckAllHitsFromRay(Vector2 vOrigin, Vector3 vDirection, float fDistance, string strTag, int iLayerMask)
	{
		ArrayList vRVal = new ArrayList();
		
		RaycastHit[] vHitList = Physics.RaycastAll(vOrigin, vDirection, fDistance, iLayerMask);

		bool bChanged = true;
		while(bChanged)
		{
			bChanged = false;
			for(int iHit = 1; iHit < vHitList.Length; ++iHit)
			{
				if(Vector3.Distance(vOrigin, vHitList[iHit].point) < Vector3.Distance(vOrigin, vHitList[iHit-1].point))
				{
					RaycastHit tempHit = vHitList[iHit-1];
					vHitList[iHit-1] = vHitList[iHit];
					vHitList[iHit] = tempHit;
					bChanged = true;
				}
			}
		}
		
		for(int iHit = 0; iHit < vHitList.Length; ++iHit)
		{
			if(vHitList[iHit].collider.tag == strTag)
				vRVal.Add(vHitList[iHit]);
			else
				break;
		}
				
		return vRVal;
	}
	
	#endregion
	
	
	#region Misc
	
	/// <summary>
	/// Determina se una cella cade nel range specificato
	/// </summary>
	/// <param name="vCenterCell">La cella di riferimento</param>
	/// <param name="vTargetCell">La cella su cui fare il controllo</param>
	/// <param name="iCellRange">Il range</param>	
	static public bool IsIntoCellRange(Vector3 vCenterCell, Vector3 vTargetCell, int iCellRange)
	{
		bool bRVal = false;
		
		if( (vTargetCell.x >= (vCenterCell.x-iCellRange) && vTargetCell.x <= (vCenterCell.x+iCellRange)) &&
			(vTargetCell.y >= (vCenterCell.y-iCellRange) && vTargetCell.y <= (vCenterCell.y+iCellRange)) )
		{
			bRVal = true;
		}
		
		return bRVal;
	}
	
	/// <summary>
	/// Dato un nome cerca il nodo corrispondente in tutta la sottogerarchia che parte dal nodo dato
	/// </summary>
	/// <param name="rRootGameObject">Il nodo radice della sottogerarchia</param>
	/// <param name="strChildName">Il nodo da cercare</param>
	public static GameObject FindInChildren(GameObject rRootGameObject, string strChildName)
	{
		GameObject rRVal = null;
		
		Transform rRootTransform = rRootGameObject.transform;
		for(int iChild = 0; (iChild < rRootTransform.childCount) && !rRVal; ++iChild)
		{
			Transform rChildTransform = rRootTransform.GetChild(iChild);
			if(rChildTransform.name == strChildName)
				rRVal = rChildTransform.gameObject;
			else
				rRVal = FindInChildren(rChildTransform.gameObject, strChildName);
		}
		
		return rRVal;
	}
	
	/// <summary>
	/// Dato un TAG (in forma di stringa) cerca il nodo corrispondente in tutta la sottogerarchia che parte dal nodo dato
	/// </summary>
	/// <param name="rRootGameObject">Il nodo radice della sottogerarchia</param>
	/// <param name="strChildTagName">Il nodo da cercare con TAG</param>
	public static GameObject FindInChildrenByTag(GameObject rRootGameObject, string strChildTagName)
	{
		GameObject rRVal = null;
		
		Transform rRootTransform = rRootGameObject.transform;
		for(int iChild = 0; (iChild < rRootTransform.childCount) && !rRVal; ++iChild)
		{
			Transform rChildTransform = rRootTransform.GetChild(iChild);
			if(rChildTransform.tag == strChildTagName)
				rRVal = rChildTransform.gameObject;
			else
				rRVal = FindInChildrenByTag(rChildTransform.gameObject, strChildTagName);
		}
		
		return rRVal;
	}
	
	/// <summary>
	/// Dato un nodo cerca il primo componente Renderer tra tutti i nodi figli
	/// </summary>
	/// <param name="rRootGameObject">Il nodo radice della sottogerarchia</param>
	public static Renderer FindRendererInChildren(GameObject rRootGameObject)
	{
		Renderer rRVal = null;
		
		if(rRootGameObject.renderer != null)
			rRVal = rRootGameObject.renderer;
		else
		{
			Transform rRootTransform = rRootGameObject.transform;
			for(int iChild = 0; (iChild < rRootTransform.childCount) && !rRVal; ++iChild)
			{
				Transform rChildTransform = rRootTransform.GetChild(iChild);
				if(rChildTransform.renderer != null)
					rRVal = rChildTransform.renderer;
				else
					rRVal = FindRendererInChildren(rChildTransform.gameObject);
			}
		}

		return rRVal;
	}
	
	/// <summary>
	/// Dato un nome cerca il materiale corrispondente in tutta la sottogerarchia che parte dal nodo dato
	/// </summary>
	/// <param name="rRootGameObject">Il nodo radice della sottogerarchia</param>
	/// <param name="rRenderer">Il componente di rendering al quale è associato il materiale trovato</param>
	public static Material FindMaterialInChildren(GameObject rRootGameObject, string strMaterialName)
	{
		Material rRVal = null;
		
		if(rRootGameObject.renderer != null)
		{
			foreach(Material rMaterial in rRootGameObject.renderer.materials)
			{
				if(rMaterial.name == strMaterialName+" (Instance)")
				{
					rRVal = rMaterial;
					break;
				}
			}
		}
		else
		{
			Transform rRootTransform = rRootGameObject.transform;
			for(int iChild = 0; (iChild < rRootTransform.childCount) && (rRVal == null); ++iChild)
				rRVal = FindMaterialInChildren(rRootTransform.GetChild(iChild).gameObject, strMaterialName);
		}
		
		return rRVal;
	}
	
	public static SkinnedMeshRenderer FindSkinnedMeshInChildren(GameObject rRootGameObject, string strMeshName)
	{
		SkinnedMeshRenderer rRVal = null;
		
		SkinnedMeshRenderer rMeshRenderer = rRootGameObject.GetComponent<SkinnedMeshRenderer>();
		if(rMeshRenderer != null)
		{
			if(rMeshRenderer.sharedMesh.name == strMeshName)
				rRVal = rMeshRenderer;
		}
		else
		{
			Transform rRootTransform = rRootGameObject.transform;
			for(int iChild = 0; (iChild < rRootTransform.childCount) && (rRVal == null); ++iChild)
				rRVal = FindSkinnedMeshInChildren(rRootTransform.GetChild(iChild).gameObject, strMeshName);
		}
		
		return rRVal;
	}
	
	public static void SwapXY(ref Vector3 vVectorToSwap)
	{
		float fTempValue = 0.0f;
		fTempValue = vVectorToSwap.x;
		vVectorToSwap.x = vVectorToSwap.y;
		vVectorToSwap.y = fTempValue;
	}
	
	public static GameObject FindObjectInScene_ByTag(string strObjectName)
	{
		GameObject rRVal = null;
		
		GameObject[] vGameObjects = GameObject.FindGameObjectsWithTag(strObjectName);
		if(vGameObjects.Length == 1)
			rRVal = vGameObjects[0];
		else
		{
			Debug.LogError("SV_Utils.FindObjectInScene() -> " + strObjectName + " non è presente la root della scena!");
			Debug.DebugBreak();
		}
		
		return rRVal;
	}
	
	#endregion
	
	
	#region DebugDraw
	
	/// <summary>
	/// Disegna una croce sullo schermo
	/// </summary>
	/// <param name="vPoint">Il centro della croce</param>
	/// <param name="fLength">La lunghezza del lato del quadrato circoscritto alla croce</param>
	/// <param name="color">Il colore scelto per il disegno</param>
	public static void DrawCross(Vector3 vPoint, float fLength, Color color)
	{
		float fHalfLength = fLength * 0.5f;
		Debug.DrawLine(vPoint - Vector3.right*fHalfLength, vPoint + Vector3.right*fHalfLength, color);
		Debug.DrawLine(vPoint - Vector3.up*fHalfLength, vPoint + Vector3.up*fHalfLength, color);
	}
	
	/// <summary>
	/// Disegna un quadrilatero passante per i quattro punti indicati
	/// </summary>
	/// <param name="color">Il colore scelto per il disegno</param>
	public static void DrawRect(Vector3 vLeftBottom, Vector3 vRightBottom, Vector3 vLeftTop, Vector3 vRightTop, Color color)
	{		
		Debug.DrawLine(vLeftBottom, vRightBottom, color, 0.0f);
		Debug.DrawLine(vRightBottom, vRightTop, color, 0.0f);
		Debug.DrawLine(vRightTop, vLeftTop, color, 0.0f);
		Debug.DrawLine(vLeftTop, vLeftBottom, color, 0.0f);
	}
	
	/// <summary>
	/// Disegna un rettangolo sul piano z=0 avente la diagonale indicata
	/// </summary>
	/// <param name="color">Il colore scelto per il disegno</param>
	public static void DrawRect(Vector3 vLeftBottom, Vector3 vRightTop, Color color)
	{
		Debug.DrawLine(new Vector3(vLeftBottom.x, vLeftBottom.y, 0.0f), new Vector3(vRightTop.x, vLeftBottom.y, 0.0f), color, 0.0f, false);
		Debug.DrawLine(new Vector3(vRightTop.x, vLeftBottom.y, 0.0f), new Vector3(vRightTop.x, vRightTop.y, 0.0f), color, 0.0f, false);
		Debug.DrawLine(new Vector3(vLeftBottom.x, vRightTop.y, 0.0f), new Vector3(vRightTop.x, vRightTop.y, 0.0f), color, 0.0f, false);
		Debug.DrawLine(new Vector3(vLeftBottom.x, vLeftBottom.y, 0.0f), new Vector3(vLeftBottom.x, vRightTop.y, 0.0f), color, 0.0f, false);
	}
	
	/// <summary>
	/// Disegna un rettangolo passante per i quattro punti indicati
	/// </summary>
	/// <param name="color">Il colore scelto per il disegno</param>
	public static void DrawRect(Rect rect, Color color)
	{
		DrawRect(new Vector3(rect.xMin, rect.yMin, 0.0f), new Vector3(rect.xMax, rect.yMax, 0.0f), color);
	}
	
	/// <summary>
	/// Disegna quattro angoli nei punti indicati
	/// </summary>
	/// <param name="fCornerLength">La lunghezza del lato del quadrato circoscritto all'angolo disegnato</param>
	/// <param name="color">Il colore scelto per il disegno</param>
	public static void DrawCorners(Vector3 vLeftBottom, Vector3 vRightBottom, Vector3 vLeftTop, Vector3 vRightTop, float fCornerLength, Color color)
	{
		Vector3 vCornerRight = (vRightBottom-vLeftBottom).normalized * fCornerLength;
		Vector3 vCornerUp = (vLeftTop-vLeftBottom).normalized * fCornerLength;
		
		Debug.DrawLine(vLeftBottom, vLeftBottom + vCornerRight, color, 0.0f, false);
		Debug.DrawLine(vLeftBottom, vLeftBottom + vCornerUp, color, 0.0f, false);
		Debug.DrawLine(vRightBottom - vCornerRight, vRightBottom, color, 0.0f, false);
		Debug.DrawLine(vRightBottom, vRightBottom + vCornerUp, color, 0.0f, false);
		Debug.DrawLine(vLeftTop, vLeftTop + vCornerRight, color, 0.0f, false);
		Debug.DrawLine(vLeftTop - vCornerUp, vLeftTop, color, 0.0f, false);
		Debug.DrawLine(vRightTop - vCornerRight, vRightTop, color, 0.0f, false);
		Debug.DrawLine(vRightTop - vCornerUp, vRightTop, color, 0.0f, false);
	}
	
	/// <summary>
	/// Disegna quattro angoli sul piano z=0 aventi centro e dimensioni come indicato
	/// </summary>
	/// <param name="color">Il colore scelto per il disegno</param>
	public static void DrawCorners(Vector3 vCenter, float fWidth, float fHeight, float fCornerLength, Color color)
	{
		float fHalfW = fWidth * 0.5f;
		float fHalfH = fHeight * 0.5f;
		Vector3 vLeftBottom = vCenter + new Vector3(-fHalfW, -fHalfH, 0.0f);
		Vector3 vRightBottom = vCenter + new Vector3(fHalfW, -fHalfH, 0.0f);
		Vector3 vLeftTop = vCenter + new Vector3(-fHalfW, fHalfH, 0.0f);
		Vector3 vRightTop = vCenter + new Vector3(fHalfW, fHalfH, 0.0f);
		
		DrawCorners(vLeftBottom, vRightBottom, vLeftTop, vRightTop, fCornerLength, color);
	}
	
	#endregion
}